/**
 * koa body-parser 包含完整的请求解析
 * Created by Tenny on 2018/7/10 14:37.
 */
const read = require('./lib/raw-read');
const qs = require('querystring');

// Allowed whitespace is defined in RFC 7159
// http://www.rfc-editor.org/rfc/rfc7159.txt
const strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;

/**
 * 普通的解析(text, raw)
 * @param buf
 * @returns {*}
 */
function parse(buf) {
  return buf;
}

/**
 * 解析数据为 JSON 格式
 * @param body
 * @returns {*}
 */
function jsonParse(body) {
  // special-case empty json body, as it's a common client-side mistake
  // TODO: maybe make this configurable or part of "strict" option
  if (!body || body.trim().length === 0) {
    return {};
  }
  if (!strictJSONReg.test(body)) {
    throw new Error('invalid JSON, only supports object and array');
  }
  return JSON.parse(body);
}

/**
 * 使用 querystring 解析数据
 * @param body
 * @returns {{}}
 */
function formParse(body) {
  if (!body || body.trim().length === 0) {
    return {};
  } else {
    return qs.parse(body);
  }
}

module.exports = function(opts) {
  opts = opts || {};
  // default json types
  let jsonTypes = [
    'application/json',
    'application/json-patch+json',
    'application/vnd.api+json',
    'application/csp-report'
  ];
  // default form types
  let formTypes = [
    'application/x-www-form-urlencoded'
  ];
  // default text types
  let textTypes = [
    'text/plain',
    'text/html',
    'text/xml'
  ];
  // 默认的 raw types
  let rawTypes = ['application/octet-stream'];

  let extendTypes = opts.extendTypes || {};

  extendType(jsonTypes, extendTypes.json);
  extendType(formTypes, extendTypes.form);
  extendType(textTypes, extendTypes.text);
  extendType(rawTypes, extendTypes.raw);

  // 配置属性
  let jsonOpts = mixedOptions({
    limit: '1mb',
    encoding: true
  }, opts.json || {});
  let textOpts = mixedOptions({
    limit: '1mb',
    encoding: true
  }, opts.text || {});
  let formOpts = mixedOptions({
    limit: '56kb',
    encoding: true
  }, opts.form || {});
  let rawOpts = mixedOptions({
    limit: '100kb'
  }, opts.raw || {});

  return async (ctx, next) => {
    /**
     * 跳过解析的情况：
     *  1. 已经解析过
     *  2. 设置了禁止解析
     *  3. 请求方式为 GET
     */
    if (ctx.request.body ||
      ctx.disableBodyParse ||
      ctx.method.toLowerCase() === 'get') {
      await next();
      return;
    }
    try {
      ctx.request.body = await parseBody(ctx);
      await next();
    } catch (err) {
      throw err;
    }
  };

  async function parseBody(ctx) {
    let r = void 0;
    if (ctx.is(jsonTypes)) {
      r = await read(ctx.req, jsonParse, jsonOpts);
    } else if (ctx.is(formTypes)) {
      r = await read(ctx.req, formParse, formOpts);
    } else if (ctx.is(textTypes)) {
      r = await read(ctx.req, parse, textOpts);
    } else if (ctx.is(rawTypes)) {
      r = await read(ctx.req, parse, rawOpts);
    }
    return r;
  }
};

/**
 * 合并参数, 只合并非 undefined 的参数
 * @param s 默认参数
 * @param o 提供的参数
 * @return {d} 合并后的参数
 */
function mixedOptions(s, o) {
  let d = {};
  for (let key in s) {
    if (typeof o[key] !== 'undefined') {
      d[key] = o[key];
    } else {
      d[key] = s[key];
    }
  }
  return d;
}

/**
 * 继承类型
 * @param original Array        原始类型集合
 * @param extend   Array|String 现在的类型
 */
function extendType(original, extend) {
  if (extend) {
    if (!Array.isArray(extend)) {
      extend = [extend];
    }
    extend.forEach(function (extend) {
      original.push(extend);
    });
  }
}